03.c - OAuth-Callback-Handler
Relevant source files
Purpose and ScopeLink copied!
This document details the OAuth callback handler endpoint at /api/auth/github/callback, which completes the GitHub OAuth and App installation flow initiated by /api/auth/github (see 3.1). The callback handler performs five critical functions: (1) verifies the OAuth state parameter to prevent CSRF attacks, (2) exchanges the authorization code for a user access token, (3) retrieves user profile data from GitHub, (4) stores the access token in an HTTP-only cookie, and (5) logs the complete installation details including the correlation between Stripe session_id and GitHub installation_id. This endpoint serves as the integration point where payment information meets repository access credentials.
For information about the different token types generated and their lifespans, see Token Types & Lifespans. For details on how the session_id is linked to the installation_id, see Payment-to-Installation Linking.
Sources: app/api/auth/github/callback/route.ts L1-L149
Callback Request FlowLink copied!
The callback handler processes GET requests from GitHub after the user completes OAuth authorization and optionally installs the GitHub App. The complete flow includes request parameter extraction, state verification, token exchange, user data fetching, and correlation logging.
Sources: app/api/auth/github/callback/route.ts L4-L149
Request ParametersLink copied!
The callback receives multiple query parameters from GitHub, some from the standard OAuth flow and others specific to GitHub App installation. The handler must process both scenarios simultaneously since GitHub sends both code and installation_id when "Request user authorization (OAuth) during installation" is enabled in the GitHub App configuration.
| Parameter | Source | Purpose | Example Value |
|---|---|---|---|
code | OAuth flow | Authorization code to exchange for access token | 7d8e9f0a1b2c3d4e |
state | OAuth flow | CSRF protection token + encoded session_id | Base64-encoded JSON string |
installation_id | GitHub App | Numeric identifier for the app installation | 12345678 |
setup_action | GitHub App | Indicates installation action type | install or update |
error | OAuth error | Error code if user denies authorization | access_denied |
Sources: app/api/auth/github/callback/route.ts L5-L14
Parameter ExtractionLink copied!
// From route.ts:5-14const code = searchParams.get("code") // OAuth authorization codeconst state = searchParams.get("state") // Base64 JSON: {sessionId, random}const error = searchParams.get("error") // OAuth error if presentconst installationId = searchParams.get("installation_id") // GitHub App installationconst setupAction = searchParams.get("setup_action") // "install" or "update"
All parameters are extracted from the URL search params using the Next.js URL API. The handler logs these parameters for debugging and correlation tracking.
Sources: app/api/auth/github/callback/route.ts L5-L17
State Verification and DecodingLink copied!
The callback implements CSRF protection through OAuth state parameter verification. The state value contains two pieces of information: a random string for CSRF protection and the Stripe session_id for payment correlation. Both must be verified before proceeding.
Sources: app/api/auth/github/callback/route.ts L23-L50
State Verification LogicLink copied!
The verification process follows these steps:
- Extract state from URL:
searchParams.get("state") - Retrieve stored state from cookie:
cookieStore.get("github_oauth_state")?.value - Compare values: If they don't match exactly, return 400 error
- Delete cookie: Remove the one-time-use state cookie after successful verification
- Decode state: Parse Base64-encoded JSON to extract
sessionId
Sources: app/api/auth/github/callback/route.ts L26-L37
State Decoding ImplementationLink copied!
// From route.ts:39-50let stripeSessionId: string | null = nulltry { const stateData = JSON.parse(Buffer.from(state, 'base64').toString('utf-8')) stripeSessionId = stateData.sessionId console.log("✅ Decoded state successfully:", stateData) console.log("✅ Extracted Stripe Session ID:", stripeSessionId)} catch (error) { console.error("❌ Failed to decode state:", error)}
If decoding fails, the handler logs the error but continues processing. This design allows GitHub App installations to succeed even if payment correlation fails, preventing payment-related issues from blocking repository access.
Sources: app/api/auth/github/callback/route.ts L39-L50
OAuth Token ExchangeLink copied!
After state verification succeeds, the handler exchanges the authorization code for an OAuth access token by calling GitHub's token endpoint. This token grants the user access to their own repositories through the dashboard.
Token Exchange RequestLink copied!
| Property | Value | Purpose |
|---|---|---|
| Endpoint | https://github.com/login/oauth/access_token | GitHub OAuth token endpoint |
| Method | POST | Standard OAuth 2.0 token exchange |
| Content-Type | application/json | Request body format |
| Accept | application/json | Response format (vs. form-encoded) |
| Body | {client_id, client_secret, code} | OAuth credentials + authorization code |
Sources: app/api/auth/github/callback/route.ts L60-L71
Token Exchange FlowLink copied!
Sources: app/api/auth/github/callback/route.ts L52-L78
Environment Variable DependenciesLink copied!
The token exchange requires two environment variables:
GITHUB_CLIENT_ID: OAuth application client ID from GitHub App settingsGITHUB_CLIENT_SECRET: OAuth application client secret (confidential)
Both are validated before making the API call. Missing credentials result in a 500 error response.
Sources: app/api/auth/github/callback/route.ts L52-L57
User Data RetrievalLink copied!
After obtaining the access token, the handler fetches the authenticated user's profile information from GitHub. This data is used for logging, correlation tracking, and ntfy notification content.
User API CallLink copied!
// From route.ts:80-88const userResponse = await fetch("https://api.github.com/user", { headers: { Authorization: `Bearer ${data.access_token}`, Accept: "application/vnd.github.v3+json", },})const userData = await userResponse.json()
Sources: app/api/auth/github/callback/route.ts L80-L88
User Data FieldsLink copied!
The GitHub user API returns a comprehensive user object. The callback handler uses the following fields:
| Field | Type | Usage | Fallback |
|---|---|---|---|
login | string | GitHub username (always present) | Used as primary identifier |
email | string|null | User's public email | Falls back to login |
id | number | GitHub user ID | Used for logging |
name | string|null | User's full name | Falls back to login |
Sources: app/api/auth/github/callback/route.ts L105-L108
Access Token StorageLink copied!
The OAuth access token is stored in an HTTP-only cookie to provide authenticated access to the user's dashboard. This token allows the user to view their own repositories but does NOT grant the owner access to those repositories (installation tokens are used for owner access, see 3.4).
Cookie ConfigurationLink copied!
// From route.ts:91-96cookieStore.set("github_access_token", data.access_token, { path: "/", secure: process.env.NODE_ENV === "production", httpOnly: true, maxAge: 60 * 60 * 24, // 1 day})
Sources: app/api/auth/github/callback/route.ts L91-L96
Cookie AttributesLink copied!
| Attribute | Value | Security Purpose |
|---|---|---|
| name | github_access_token | Cookie identifier |
| path | / | Available to all routes |
| secure | true in production | HTTPS-only transmission |
| httpOnly | true | Prevents JavaScript access (XSS mitigation) |
| maxAge | 86400 seconds (24 hours) | Token expiration window |
The httpOnly flag is critical for security—it prevents malicious JavaScript from reading the token, protecting against XSS attacks. The secure flag ensures the token is only transmitted over HTTPS in production environments.
Sources: app/api/auth/github/callback/route.ts L91-L96
Installation Correlation LoggingLink copied!
When the callback receives an installation_id (indicating GitHub App installation), it logs comprehensive details to Vercel's console for manual correlation with Stripe payments. This logging is the primary mechanism for linking payments to repository access grants.
Logged Installation DetailsLink copied!
Sources: app/api/auth/github/callback/route.ts L99-L111
Log StructureLink copied!
The log output includes decorative borders (80 equals signs) for visual separation in console logs, making it easier to locate installation events when reviewing Vercel logs.
// From route.ts:100-111console.log("=".repeat(80))console.log("🎉 NEW GITHUB APP INSTALLATION")console.log("=".repeat(80))console.log(`Stripe Session ID: ${stripeSessionId || "NOT PROVIDED"}`)console.log(`Installation ID: ${installationId}`)console.log(`GitHub Username: ${userData.login}`)console.log(`GitHub Email: ${userData.email || "Not public"}`)console.log(`GitHub User ID: ${userData.id}`)console.log(`User Name: ${userData.name || "Not provided"}`)console.log(`Setup Action: ${setupAction || "install"}`)console.log(`Timestamp: ${new Date().toISOString()}`)console.log("=".repeat(80))
Sources: app/api/auth/github/callback/route.ts L99-L111
Correlation FieldsLink copied!
| Field | Source | Purpose |
|---|---|---|
| Stripe Session ID | Decoded from state parameter | Links installation to payment |
| Installation ID | URL query parameter | GitHub App installation identifier |
| GitHub Username | userData.login | Primary user identifier |
| GitHub Email | userData.email | Contact information (may be null) |
| GitHub User ID | userData.id | Numeric GitHub user ID |
| User Name | userData.name | Full name (may be null) |
| Setup Action | URL query parameter | Installation type (install/update) |
| Timestamp | new Date().toISOString() | Installation time in ISO 8601 format |
The owner uses these logs to manually correlate payments from Stripe with GitHub installations, enabling them to determine which installation_id should be used in the admin panel to generate access tokens.
Sources: app/api/auth/github/callback/route.ts L103-L110
ntfy Notification DispatchLink copied!
After logging installation details, the callback sends a notification to ntfy.sh, triggering the automated repository cloning pipeline. This notification serves as the event that initiates the automation scripts (see 5.2 and 5.3).
Notification Payload ConstructionLink copied!
Sources: app/api/auth/github/callback/route.ts L114-L120
Notification ImplementationLink copied!
// From route.ts:114-132const ntfyTopic = process.env.NTFY_TOPIC || "godeep-wiki-payments"const customerEmail = userData.email || userData.loginconst customerName = userData.name || userData.loginconst timestamp = new Date().toLocaleString('en-US', { timeZone: 'America/New_York' })const sessionShort = stripeSessionId ? stripeSessionId.slice(-12) : 'Not linked'const message = `✅ GitHub Connected!\n\n🔑 Installation ID: ${installationId}\n📧 Customer: ${customerEmail}\n🔑 Match ID: ${sessionShort}\n⏰ Time: ${timestamp}\n\n🤖 Automation will now:\n• Clone the repository\n• Create private repo in your account\n\nCheck terminal for progress!`await fetch(`https://ntfy.sh/${ntfyTopic}`, { method: "POST", headers: { "Content-Type": "text/plain", Title: "Step 2: GitHub Connected - GoDeep.wiki", Priority: "high", Tags: "white_check_mark,rocket", }, body: message,})
Sources: app/api/auth/github/callback/route.ts L114-L132
Notification HeadersLink copied!
| Header | Value | Purpose |
|---|---|---|
| Content-Type | text/plain | Plain text message format |
| Title | Step 2: GitHub Connected - GoDeep.wiki | Notification title in ntfy app |
| Priority | high | Ensures notification visibility |
| Tags | white_check_mark,rocket | Emoji tags for visual identification |
The notification is sent asynchronously without blocking the user redirect. If the ntfy call fails, an error is logged but the user flow continues normally.
Sources: app/api/auth/github/callback/route.ts L123-L132
Match ID CorrelationLink copied!
The Match ID (last 12 characters of session_id) provides a shortened identifier for quick correlation:
- Full session_id:
cs_test_a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6q7R8s9T0u1V2w3X4y5Z6 - Match ID:
w3X4y5Z6
This abbreviated identifier appears in both the Stripe dashboard and the ntfy notification, allowing the owner to quickly match payments to installations without comparing full session IDs.
Sources: app/api/auth/github/callback/route.ts L118
Error Handling and RedirectsLink copied!
The callback handler implements multiple error scenarios and redirect paths to handle various failure modes gracefully.
Error ScenariosLink copied!
Sources: app/api/auth/github/callback/route.ts L19-L148
Error Response TableLink copied!
| Scenario | HTTP Status | Response/Redirect | User Impact |
|---|---|---|---|
| OAuth error (user denial) | 302 | Redirect to /success?error={error} | User sees error on success page |
| State verification failure | 400 | "Invalid state" plain text | Connection fails, user must retry |
| Missing GitHub credentials | 500 | "GitHub configuration missing" | System misconfiguration |
| Token exchange error | 302 | Redirect to /success?error=token_error | User sees error on success page |
| Next.js redirect | - | Re-throw error | Normal redirect behavior |
| Other errors | - | Log error, continue redirect | Error logged but user flow continues |
Sources: app/api/auth/github/callback/route.ts L19-L21
app/api/auth/github/callback/route.ts L32-L34
app/api/auth/github/callback/route.ts L55-L57
app/api/auth/github/callback/route.ts L75-L77
app/api/auth/github/callback/route.ts L140-L146
Next.js Redirect HandlingLink copied!
The callback must distinguish between genuine errors and Next.js redirect throws:
// From route.ts:140-146} catch (error: any) { // Don't log Next.js redirect errors (they're normal) if (error?.digest?.startsWith('NEXT_REDIRECT')) { throw error } console.error("GitHub auth error:", error)}
Next.js redirects are implemented as thrown errors with a digest property starting with 'NEXT_REDIRECT'. These must be re-thrown to complete the redirect, not logged as errors.
Sources: app/api/auth/github/callback/route.ts L140-L146
Security ConsiderationsLink copied!
The callback handler implements multiple security layers to protect against common OAuth vulnerabilities.
Security MechanismsLink copied!
| Mechanism | Implementation | Threat Mitigated |
|---|---|---|
| CSRF Protection | OAuth state parameter verification | Cross-Site Request Forgery attacks |
| Cookie Security | httpOnly flag on access token cookie | XSS token theft |
| HTTPS Enforcement | secure flag in production | Man-in-the-middle attacks |
| One-Time State | State cookie deleted after verification | Replay attacks |
| Server-Side Secrets | Client secret never exposed to client | Credential leakage |
| Signature Verification | (Planned for GitHub webhooks) | Event forgery |
Sources: app/api/auth/github/callback/route.ts L23-L37
app/api/auth/github/callback/route.ts L91-L96
Credential HandlingLink copied!
The callback requires two confidential environment variables:
GITHUB_CLIENT_SECRET: Never sent to the client, only used server-side in token exchangeGITHUB_PRIVATE_KEY: Not used in this callback but available for installation token generation (see 6.2)
These credentials are read from process.env and never logged or exposed in responses.
Sources: app/api/auth/github/callback/route.ts L52-L53
State Parameter SecurityLink copied!
The state parameter serves dual purposes:
- CSRF token: Random UUID prevents cross-site request attacks
- Session correlation: Embedded
session_idlinks payment to installation
The state is:
- Generated server-side in 3.1
- Stored in HTTP-only cookie
- Verified on callback
- Immediately deleted after verification (one-time use)
Sources: app/api/auth/github/callback/route.ts L26-L37
Integration PointsLink copied!
The OAuth callback handler connects to multiple system components, serving as a critical integration point in the overall architecture.
Sources: app/api/auth/github/callback/route.ts L1-L149
Upstream DependenciesLink copied!
- [3.1] OAuth Initiation: Creates the
github_oauth_statecookie that this handler verifies - [2.2] Success Page: Embeds
session_idin the OAuth URL, which flows through to the state parameter - GitHub Platform: Provides the callback redirect with code, state, and installation_id
Downstream ConsumersLink copied!
- [2.3] Thank You Page: Final redirect destination for successful installations
- [7] Dashboard: Uses the stored
github_access_tokencookie for authenticated API calls - [5.2] Automation Scripts: Triggered by ntfy notifications sent from this handler
- [6.3] Manual Workflow: Owner reads Vercel logs to correlate
session_idwithinstallation_id
Sources: CLAUDE.md L44-L67
Refresh this wiki
Last indexed: 23 November 2025 (922b35)
On this page
- OAuth Callback Handler
- Purpose and Scope
- Callback Request Flow
- Request Parameters
- Parameter Extraction
- State Verification and Decoding
- State Verification Logic
- State Decoding Implementation
- OAuth Token Exchange
- Token Exchange Request
- Token Exchange Flow
- Environment Variable Dependencies
- User Data Retrieval
- User API Call
- User Data Fields
- Access Token Storage
- Cookie Configuration
- Cookie Attributes
- Installation Correlation Logging
- Logged Installation Details
- Log Structure
- Correlation Fields
- ntfy Notification Dispatch
- Notification Payload Construction
- Notification Implementation
- Notification Headers
- Match ID Correlation
- Error Handling and Redirects
- Error Scenarios
- Error Response Table
- Next.js Redirect Handling
- Security Considerations
- Security Mechanisms
- Credential Handling
- State Parameter Security
- Integration Points
- Upstream Dependencies
- Downstream Consumers
Ask Devin about godeep.wiki-jb